/**
 * Radio Story Cards - using RFID tags to track physical story cards in an agile/lean environment.
 *
 * See http://www.radiostorycards.com for more information.
 *
 *
 * Copyright (C) 2011, Ted M. Young
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package com.radiostorycards.gui;

import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;
import com.jgoodies.looks.windows.WindowsLookAndFeel;
import com.radiostorycards.comm.RfidReaderType;
import com.radiostorycards.comm.SerialReader;
import com.radiostorycards.domain.cards.Column;
import com.radiostorycards.domain.cards.RadioCard;
import com.radiostorycards.domain.tags.RfidTag;
import com.radiostorycards.event.TagSwipedEvent;
import com.radiostorycards.persistence.RadioCardStore;
import gnu.io.NoSuchPortException;
import gnu.io.PortInUseException;
import gnu.io.UnsupportedCommOperationException;
import org.bushe.swing.event.annotation.AnnotationProcessor;
import org.bushe.swing.event.annotation.EventSubscriber;

import javax.swing.*;
import javax.swing.border.EtchedBorder;
import javax.swing.border.TitledBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.io.IOException;
import java.util.TooManyListenersException;

/**
 * The starting point for the application. Note that this class was created using JFormDesigner 5.0, so it's best if
 * the code-generated Swing code is either left alone, or follows the existing format.
 *
 * @author Ted Young <tedyoung@gmail.com> @JitterTed
 */
@SuppressWarnings({"FieldCanBeLocal"})
public class RadioCardManagerFrame extends JFrame {

  private RfidTag _swipedRfidTag;
  private SwipeTune _swipeTune;
  private final RadioCardStore _store;
  private RadioCard _currentRadioCard;

  public RadioCardManagerFrame() {
    setJGoodiesLookAndFeel();
    initComponents();
    startSerialReader();
    clearFields();
    addDefaultFocusListeners();
    _swipeTune = initializeMusicPlayer();
    _store = RadioCardStore.loadStoreFrom("RadioCardStore");
  }

  private void setJGoodiesLookAndFeel() {
    try {
      UIManager.setLookAndFeel(new WindowsLookAndFeel());
    } catch (UnsupportedLookAndFeelException e) {
      // let the default look and feel be used
    }
  }

  private SwipeTune initializeMusicPlayer() {
    SwipeTune swipeTune = new SwipeTune();
    swipeTune.playSwiped();
    return swipeTune;
  }

  private void addDefaultFocusListeners() {
    myNewTagPanel.addComponentListener(new ComponentAdapter() {
      @Override
      public void componentShown(ComponentEvent e) {
        myLinkToJira.requestFocusInWindow();
      }
    });
    myExistingTagPanel.addComponentListener(new ComponentAdapter() {
      @Override
      public void componentShown(ComponentEvent e) {
        myMoveToNextStateButton.requestFocusInWindow();
      }
    });
  }

  private void okButtonAction() {
    if (myLinkToJira.getText().isEmpty()) {
      myLinkToJira.setBackground(Color.pink);
      myLinkToJira.requestFocusInWindow();
    } else {
      myLinkToJira.setBackground(Color.white);
      attachToJira();
    }
  }

  private void attachToJira() {
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        RadioCard radioCard = RadioCard.createWithTag(getRfidTag());
        _currentRadioCard = radioCard;
        radioCard.linkToJira(getJira());
        String selectedColumn = myInitialStateChoices.getSelectedItem().toString();
        radioCard.transitionTo(Column.toEnum(selectedColumn));
        _store.add(radioCard);
        saveCardStore();
        doKnownTagSeen(radioCard);
        _swipeTune.playSuccess();
      }
    });
  }

  private void saveCardStore() {
    try {
      _store.saveStoreTo("RadioCardStore");
    } catch (IOException e) {
      e.printStackTrace();
      JOptionPane.showMessageDialog(this,
          "Unable to store RadioCard data to disk. See stack trace for details.",
          "Error: Unable to Save Data",
          JOptionPane.ERROR_MESSAGE);
    }
  }

  private RfidTag getRfidTag() {
    return _swipedRfidTag;
  }

  private String getJira() {
    return myLinkToJira.getText();
  }

  private void clearButtonAction() {
    clearFields();
  }

  private void moveToNextStateButtonActionPerformed() {
    if (_currentRadioCard == null) {
      throw new IllegalStateException("Should not be able to click this button if there has not been a card swiped.");
    }
    _currentRadioCard.transitionToNextState();
    saveCardStore();
    showCurrentCardInfo();
    _swipeTune.playSuccess();
  }

  private void attachToJiraActionPerformed() {
    attachToJira();
  }

  private void clearFields() {
    myStatusLabel.setText("");
    myRfidTag.setText("");
    myCurrentState.setText("");
    myCurrentJira.setText("");
    myNextState.setText("");
    myLinkToJira.setText("");
    myLinkToJira.setBackground(Color.white);
    _currentRadioCard = null;
    showCard("blank");
  }

  private void moveToNextStateButtonActionPerformed(ActionEvent e) {
    // TODO add your code here
  }

  private void initComponents() {
    // JFormDesigner - Component initialization - DO NOT MODIFY  //GEN-BEGIN:initComponents
    // Generated using JFormDesigner non-commercial license
    myStatusLabel = new JLabel();
    myCardPanel = new JPanel();
    myBlankPanel = new JPanel();
    myWaitingForCardLabel = new JLabel();
    myExistingTagPanel = new JPanel();
    myRfidTagLabel = new JLabel();
    myRfidTag = new JLabel();
    myLinkToJiraLabel2 = new JLabel();
    myCurrentJira = new JLabel();
    myCurrentStateLabel = new JLabel();
    myCurrentState = new JLabel();
    myTransitionDateLabel = new JLabel();
    myTransitionDate = new JLabel();
    myNextStateLabel = new JLabel();
    myNextState = new JLabel();
    myMoveToNextStateButton = new JButton();
    myNewTagPanel = new JPanel();
    myNewRfidTagLabel = new JLabel();
    myNewRfidTag = new JLabel();
    myLinkToJiraLabel = new JLabel();
    myInitialStateOfCardLabel = new JLabel();
    myLinkToJira = new JTextField();
    myInitialStateChoices = new JComboBox();
    myLinkButton = new JButton();
    myClearButton = new JButton();
    CellConstraints cc = new CellConstraints();

    //======== this ========
    setTitle("RFID Tracking System");
    setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    Container contentPane = getContentPane();
    contentPane.setLayout(new GridBagLayout());
    ((GridBagLayout)contentPane.getLayout()).columnWidths = new int[] {10, 0, 10, 0};
    ((GridBagLayout)contentPane.getLayout()).rowHeights = new int[] {12, 0, 15, 0, 0, 12, 0};
    ((GridBagLayout)contentPane.getLayout()).columnWeights = new double[] {0.0, 1.0, 0.0, 1.0E-4};
    ((GridBagLayout)contentPane.getLayout()).rowWeights = new double[] {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0E-4};

    //---- myStatusLabel ----
    myStatusLabel.setText("Swipe The Card...");
    myStatusLabel.setFont(new Font("Calibri", Font.PLAIN, 32));
    contentPane.add(myStatusLabel, new GridBagConstraints(1, 1, 1, 1, 0.0, 0.0,
      GridBagConstraints.CENTER, GridBagConstraints.BOTH,
      new Insets(0, 0, 0, 0), 0, 0));

    //======== myCardPanel ========
    {
      myCardPanel.setLayout(new CardLayout());

      //======== myBlankPanel ========
      {
        myBlankPanel.setLayout(new BorderLayout());

        //---- myWaitingForCardLabel ----
        myWaitingForCardLabel.setText("Waiting for Card...");
        myWaitingForCardLabel.setFont(new Font("Calibri", Font.PLAIN, 48));
        myWaitingForCardLabel.setHorizontalAlignment(SwingConstants.CENTER);
        myBlankPanel.add(myWaitingForCardLabel, BorderLayout.CENTER);
      }
      myCardPanel.add(myBlankPanel, "blank");

      //======== myExistingTagPanel ========
      {
        myExistingTagPanel.setBorder(new TitledBorder(new EtchedBorder(), "Existing Tag", TitledBorder.LEADING, TitledBorder.DEFAULT_POSITION,
          new Font("Consolas", Font.BOLD, 20)));
        myExistingTagPanel.setLayout(new FormLayout(
          "131dlu, 6dlu, 131dlu:grow",
          "5*(default, $lgap), default"));

        //---- myRfidTagLabel ----
        myRfidTagLabel.setText("RFID Tag:");
        myRfidTagLabel.setFont(new Font("Calibri", Font.PLAIN, 20));
        myExistingTagPanel.add(myRfidTagLabel, cc.xy(1, 1));

        //---- myRfidTag ----
        myRfidTag.setText("00:11:22:33:44");
        myRfidTag.setFont(new Font("Calibri", Font.PLAIN, 20));
        myExistingTagPanel.add(myRfidTag, cc.xy(3, 1));

        //---- myLinkToJiraLabel2 ----
        myLinkToJiraLabel2.setText("Currently Linked To:");
        myLinkToJiraLabel2.setFont(new Font("Calibri", Font.PLAIN, 24));
        myExistingTagPanel.add(myLinkToJiraLabel2, cc.xy(1, 3));

        //---- myCurrentJira ----
        myCurrentJira.setText("JIRA-123456");
        myCurrentJira.setFont(new Font("Consolas", Font.PLAIN, 24));
        myExistingTagPanel.add(myCurrentJira, cc.xy(3, 3));

        //---- myCurrentStateLabel ----
        myCurrentStateLabel.setText("Current State of Card:");
        myCurrentStateLabel.setFont(new Font("Calibri", Font.PLAIN, 24));
        myExistingTagPanel.add(myCurrentStateLabel, cc.xy(1, 5));

        //---- myCurrentState ----
        myCurrentState.setFont(new Font("Calibri", Font.PLAIN, 24));
        myCurrentState.setText("currentState");
        myExistingTagPanel.add(myCurrentState, cc.xy(3, 5));

        //---- myTransitionDateLabel ----
        myTransitionDateLabel.setText("Changed State On:");
        myTransitionDateLabel.setFont(new Font("Calibri", Font.PLAIN, 24));
        myExistingTagPanel.add(myTransitionDateLabel, cc.xy(1, 7));

        //---- myTransitionDate ----
        myTransitionDate.setFont(new Font("Calibri", Font.PLAIN, 24));
        myTransitionDate.setText("currentState");
        myExistingTagPanel.add(myTransitionDate, cc.xy(3, 7));

        //---- myNextStateLabel ----
        myNextStateLabel.setText("Next State:");
        myNextStateLabel.setFont(new Font("Calibri", Font.PLAIN, 24));
        myExistingTagPanel.add(myNextStateLabel, cc.xy(1, 9));

        //---- myNextState ----
        myNextState.setFont(new Font("Calibri", Font.PLAIN, 24));
        myNextState.setText("nextState");
        myExistingTagPanel.add(myNextState, cc.xy(3, 9));

        //---- myMoveToNextStateButton ----
        myMoveToNextStateButton.setText("Move to Next State");
        myMoveToNextStateButton.setMnemonic('N');
        myMoveToNextStateButton.setFont(myMoveToNextStateButton.getFont().deriveFont(myMoveToNextStateButton.getFont().getSize() + 10f));
        myMoveToNextStateButton.addActionListener(new ActionListener() {
          @Override
          public void actionPerformed(ActionEvent e) {
            moveToNextStateButtonActionPerformed(e);
            moveToNextStateButtonActionPerformed();
            moveToNextStateButtonActionPerformed();
            moveToNextStateButtonActionPerformed();
          }
        });
        myExistingTagPanel.add(myMoveToNextStateButton, cc.xywh(1, 11, 3, 1));
      }
      myCardPanel.add(myExistingTagPanel, "existingTag");

      //======== myNewTagPanel ========
      {
        myNewTagPanel.setBorder(new TitledBorder(new EtchedBorder(), "New Tag", TitledBorder.LEADING, TitledBorder.DEFAULT_POSITION,
          new Font("Consolas", Font.BOLD, 20)));
        myNewTagPanel.setLayout(new FormLayout(
          "125dlu, $lcgap, default, $lcgap, default:grow",
          "default, $lgap, 25dlu, 2*($pgap, default), $lgap, default"));

        //---- myNewRfidTagLabel ----
        myNewRfidTagLabel.setText("RFID Tag:");
        myNewRfidTagLabel.setFont(new Font("Calibri", Font.PLAIN, 20));
        myNewTagPanel.add(myNewRfidTagLabel, cc.xy(1, 1));

        //---- myNewRfidTag ----
        myNewRfidTag.setText("00:11:22:33:44");
        myNewRfidTag.setFont(new Font("Calibri", Font.PLAIN, 20));
        myNewTagPanel.add(myNewRfidTag, cc.xy(5, 1));

        //---- myLinkToJiraLabel ----
        myLinkToJiraLabel.setText("Link to JIRA:");
        myLinkToJiraLabel.setFont(new Font("Calibri", Font.PLAIN, 24));
        myLinkToJiraLabel.setLabelFor(myLinkToJira);
        myNewTagPanel.add(myLinkToJiraLabel, cc.xy(1, 3));

        //---- myInitialStateOfCardLabel ----
        myInitialStateOfCardLabel.setText("Initial State of Card:");
        myInitialStateOfCardLabel.setFont(new Font("Calibri", Font.PLAIN, 24));
        myNewTagPanel.add(myInitialStateOfCardLabel, cc.xy(1, 5));

        //---- myLinkToJira ----
        myLinkToJira.setFont(new Font("Consolas", Font.PLAIN, 24));
        myLinkToJira.addActionListener(new ActionListener() {
          @Override
          public void actionPerformed(ActionEvent e) {
            attachToJiraActionPerformed();
          }
        });
        myNewTagPanel.add(myLinkToJira, cc.xy(5, 3, CellConstraints.FILL, CellConstraints.DEFAULT));

        //---- myInitialStateChoices ----
        myInitialStateChoices.setModel(new DefaultComboBoxModel(new String[] {
          "Backlog",
          "To-Do",
          "Doing",
          "Ready for Acceptance",
          "Ready for Testing",
          "In Test",
          "Done-Done"
        }));
        myInitialStateChoices.setFont(myInitialStateChoices.getFont().deriveFont(myInitialStateChoices.getFont().getSize() + 10f));
        myNewTagPanel.add(myInitialStateChoices, cc.xy(5, 5, CellConstraints.FILL, CellConstraints.DEFAULT));

        //---- myLinkButton ----
        myLinkButton.setText("Link and Set Initial State");
        myLinkButton.setMnemonic('L');
        myLinkButton.setFont(myLinkButton.getFont().deriveFont(myLinkButton.getFont().getSize() + 12f));
        myLinkButton.addActionListener(new ActionListener() {
          @Override
          public void actionPerformed(ActionEvent e) {
            okButtonAction();
          }
        });
        myNewTagPanel.add(myLinkButton, cc.xywh(1, 7, 5, 1, CellConstraints.FILL, CellConstraints.DEFAULT));
      }
      myCardPanel.add(myNewTagPanel, "newTag");
    }
    contentPane.add(myCardPanel, new GridBagConstraints(1, 2, 1, 1, 0.0, 0.0,
      GridBagConstraints.CENTER, GridBagConstraints.BOTH,
      new Insets(0, 0, 0, 0), 0, 0));

    //---- myClearButton ----
    myClearButton.setText("Clear");
    myClearButton.setMnemonic('C');
    myClearButton.setFont(myClearButton.getFont().deriveFont(myClearButton.getFont().getSize() + 10f));
    myClearButton.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        clearButtonAction();
      }
    });
    contentPane.add(myClearButton, new GridBagConstraints(1, 4, 1, 1, 0.0, 0.0,
      GridBagConstraints.EAST, GridBagConstraints.VERTICAL,
      new Insets(0, 0, 0, 0), 0, 0));
    pack();
    setLocationRelativeTo(getOwner());
    // JFormDesigner - End of component initialization  //GEN-END:initComponents
  }

  // JFormDesigner - Variables declaration - DO NOT MODIFY  //GEN-BEGIN:variables
  // Generated using JFormDesigner non-commercial license
  private JLabel myStatusLabel;
  private JPanel myCardPanel;
  private JPanel myBlankPanel;
  private JLabel myWaitingForCardLabel;
  private JPanel myExistingTagPanel;
  private JLabel myRfidTagLabel;
  private JLabel myRfidTag;
  private JLabel myLinkToJiraLabel2;
  private JLabel myCurrentJira;
  private JLabel myCurrentStateLabel;
  private JLabel myCurrentState;
  private JLabel myTransitionDateLabel;
  private JLabel myTransitionDate;
  private JLabel myNextStateLabel;
  private JLabel myNextState;
  private JButton myMoveToNextStateButton;
  private JPanel myNewTagPanel;
  private JLabel myNewRfidTagLabel;
  private JLabel myNewRfidTag;
  private JLabel myLinkToJiraLabel;
  private JLabel myInitialStateOfCardLabel;
  private JTextField myLinkToJira;
  private JComboBox myInitialStateChoices;
  private JButton myLinkButton;
  private JButton myClearButton;
  // JFormDesigner - End of variables declaration  //GEN-END:variables

  @EventSubscriber(eventClass = TagSwipedEvent.class)
  public void updateStatus(TagSwipedEvent tagSwipedEvent) {
    _swipeTune.playSwiped();
    tagSwiped(tagSwipedEvent.getTag());
  }

  public void tagSwiped(final RfidTag rfidTag) {
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        clearFields();
        myRfidTag.setText(rfidTag.toString());
        myNewRfidTag.setText(rfidTag.toString());
        _swipedRfidTag = rfidTag;
        _currentRadioCard = _store.findByTag(rfidTag);
        showCurrentCardInfo();
      }
    });
  }

  private void showCurrentCardInfo() {
    if (_currentRadioCard == null) {
      doNewTagSeen();
    } else {
      doKnownTagSeen(_currentRadioCard);
    }
  }

  private void doKnownTagSeen(RadioCard radioCard) {
    showCard("existingTag");
    myStatusLabel.setText("This RFID Tag is being tracked.");
    myStatusLabel.setForeground(Color.BLUE.darker());
    myCurrentJira.setText(radioCard.getJiraNumber());
    myCurrentState.setText(radioCard.currentColumn().name());
    String dateText = radioCard.dateTimeOfTransition().toString("MM/dd/yyyy h:mma");
    myTransitionDate.setText(dateText);
  }

  private void doNewTagSeen() {
    showCard("newTag");
    myStatusLabel.setText("This RFID Tag is new to the system.");
    myStatusLabel.setForeground(Color.GREEN.darker());
  }

  private void showCard(String cardName) {
    ((CardLayout) myCardPanel.getLayout()).show(myCardPanel, cardName);
  }

  public static void main(String[] args) {
    RadioCardManagerFrame radioCardManagerFrame = new RadioCardManagerFrame();
    AnnotationProcessor.process(radioCardManagerFrame);
    radioCardManagerFrame.setVisible(true);
  }

  private void startSerialReader() {
    try {
      SerialReader.createSerialReaderFor(RfidReaderType.Parallax);
    } catch (NoSuchPortException e) {
      System.err.println("Can't connect to " + SerialReader.RFID_READER_COM_PORT_NAME + ": serial port.");
      e.printStackTrace();
      System.exit(-1);
    } catch (PortInUseException e) {
      System.err.println("Serial port " + SerialReader.RFID_READER_COM_PORT_NAME + ": is in use.");
      e.printStackTrace();
      System.exit(-1);
    } catch (IOException e) {
      e.printStackTrace();
      System.exit(-1);
    } catch (TooManyListenersException e) {
      e.printStackTrace();
      System.exit(-1);
    } catch (UnsupportedCommOperationException e) {
      e.printStackTrace();
      System.exit(-1);
    }
  }

}
